package cn.fzc.example.redis.distributedlock;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: fu.zc
 * @Date: 2020/8/4 16:45
 * @Description:
 */
@Slf4j
public class DistributeLock implements ILock {


    // 线程变量
    private ThreadLocal<String> threadId = new ThreadLocal<String>();


    private final StringRedisTemplate stringRedisTemplate;

    public DistributeLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    @Override
    public void lock(String lockKey) {
        Assert.notNull(lockKey, "lockkey can`t be null");
        BoundValueOperations<String, String> boundValueOperations = stringRedisTemplate.boundValueOps(lockKey);
        while (true) {
            String value = boundValueOperations.get();
            if (StringUtils.equals(value, threadId.get())) {
                boundValueOperations.expire(LOCK_MAX_EXIST_TIME, TimeUnit.SECONDS);
                break;
            }

            if (boundValueOperations.setIfAbsent(lockKey)) {
                // 每次获取锁时，必须重新生成id值
                String keyUniqueId = UUID.randomUUID().toString(); // 生成key的唯一值
                threadId.set(keyUniqueId);
                // 显设置value，再设置过期日期，否则过期日期无效
                boundValueOperations.set(String.valueOf(keyUniqueId));
                // 为了避免一个用户拿到锁后，进行过程中没有正常释放锁，这里设置一个默认过期实际，这段非常重要，如果没有，则会造成死锁
                boundValueOperations.expire(LOCK_MAX_EXIST_TIME, TimeUnit.SECONDS);
                // 拿到锁后，跳出循环
                break;
            } else {
                try {
                    // 短暂休眠，nano避免出现活锁
                    Thread.sleep(10, (int) (Math.random() * 500));
                } catch (InterruptedException e) {
                    break;
                }
            }
        }


    }

    @Override
    public void unlock(String lockKey) {
        BoundValueOperations<String,String> keyBoundValueOperations = stringRedisTemplate.boundValueOps(lockKey);
        String lockValue = keyBoundValueOperations.get();
        if(!StringUtils.isEmpty(lockValue) && lockValue.equals(threadId.get())){
            stringRedisTemplate.delete(lockKey);
        }else{
            log.warn("key=[{}]已经变释放了，本次不执行释放. 线程Id[{}] ", lockKey, lockValue);
        }
    }
}
